﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using SharpPcap.WinPcap;
using BasePlatformCommon;
using System.Timers;

namespace BasePlatformNet
{
	public class PacketSender
	{
		private bool useWinPcap = true;								// 使用 winpcap 发包标记

		private string ip = "";										// ip 地址

		private WinPcapDevice sendDevice = null;					// SharpPcap 发送器
		private PacketBuilder pb;									// 数据包构建器
		private Random myRandom = null;								// 随机数生成器

		private byte[] framePacket = null;							// 帧包
		private byte[] dispConfigPacket = null;						// 显示设置包

		public FramePacketPara framePacketPara = null;				// 帧包发送参数对象
		public DispConfigPacketPara dispConfigPacketPara = null;	// 显示设置包发送参数对象

		public bool topologyDetectEnable = false;					// 拓扑结构链检测使能	开启/关闭

		private bool subConfigSendEnable = false;					// 分控配置数据周期发送使能
		private int[] subConfigMemAddrArray = null;					// 分控配置包地址列表
		private List<byte[]> subConfigPacketData = null;			// 分控配置包数据列表	10 * 256 bytes
		private int subConfigPacketIndex = 0;						// 分控配置包发送计数

		private int userColorType = 0;								// 用户在界面上选择的颜色模式，0-三色（默认），1-四色纯，2-四色增强

		private Stopwatch sw = new Stopwatch();						// 性能秒表

		/******************************************************************************/

		private static Socket socket = null;						// 发包Socket
		private static IPEndPoint server = null;
		private static EndPoint epServer = null;

		private static string breadcastIP = "192.168.100.255";		// 广播网段 192.168.100.*
        private byte[] broadcastIPByte = new byte[4] { 192, 168, 100, 255 };
		private static string breadcastIPMask = "192.168.100.";		// 广播网段掩码 192.168.100.*

        private SocketSendMode socketSendMode = SocketSendMode.Asyn;	// socket 发包模式

        private NetDevice device;
		
        private int packetNumPerGroup = 10; // 每个socket包队列长度

        public Action ExceptionHappen;

		/******************************************************************************/

        private void onCatchException() {
            if (ExceptionHappen != null) {
                ExceptionHappen();
            }
        }
		// 构造函数
		public PacketSender(PacketBuilder packetBuilder)
		{
			initSocket();

			sendDevice = null;

			myRandom = new Random();

			pb = packetBuilder;

			framePacket = pb.buildFramePacket(0, 0);
			dispConfigPacket = null;

			subConfigMemAddrArray = new int[10];
			subConfigMemAddrArray[0] = 0x00100;
			subConfigMemAddrArray[1] = 0x01000;
			subConfigMemAddrArray[2] = 0x03000;
			subConfigMemAddrArray[3] = 0x03100;
			subConfigMemAddrArray[4] = 0x04000;
			subConfigMemAddrArray[5] = 0x04100;
			subConfigMemAddrArray[6] = 0x07000;
			subConfigMemAddrArray[7] = 0x07100;
			subConfigMemAddrArray[8] = 0x07200;
			subConfigMemAddrArray[9] = 0x08000;

			subConfigPacketData = new List<byte[]>();
			for (int i = 0; i < 10; i++)
			{
				subConfigPacketData.Add(new byte[256]);
			}
		}

		// 初始化 Socket
		public void initSocket()
		{
			socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			
			socket.SendBufferSize = 16384;
			socket.ReceiveBufferSize = 16384;

			Log.NormalLogger.Info("socket.SendBufferSize = " + socket.SendBufferSize + ", socket.ReceiveBufferSize = " + socket.ReceiveBufferSize);

			// 设置服务器端IP地址和对应端口，广播网段 192.168.100.*
            server = new IPEndPoint(new IPAddress(broadcastIPByte), 21930);

			epServer = (EndPoint)server;

            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
		}

        public bool BindIP()
        {
            try
            {
                // 固定到IP192.168.100.1, 端口21930
                if (!socket.IsBound)
                {
                    socket.Bind((EndPoint)new IPEndPoint(new IPAddress(new byte[4] { 192, 168, 100, 1 }), 29130));
                }

                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

		// 设置是否使用 winpcap 发包
		public void setUseWinPcap(bool useWinPcap)
		{
			this.useWinPcap = useWinPcap;
			pb.setUseWinPcap(useWinPcap);

			// 更新预构造帧包 + 显示设置包
			if (framePacketPara != null)
			{
				framePacket = pb.buildFramePacket(framePacketPara);
			}
			else
			{
				framePacket = pb.buildFramePacket(0, 0);
			}

			if (dispConfigPacketPara != null)
			{
				dispConfigPacket = pb.buildDispConfigPacket(dispConfigPacketPara);
			}
			else
			{
				dispConfigPacket = pb.buildDispConfigPacket(false, false);
			}
		}

		// 设置 socket 发包模式
		public void setSocketSendMode(SocketSendMode socketSendMode)
		{
			this.socketSendMode = socketSendMode;
		}

		// 设置 SharpPcap 发送器
		public void setDevice(WinPcapDevice device)
		{
            sendDevice = device;
		}

		// 设置 ip 地址
		public void setIPAddress(string ip)
		{
			this.ip = ip;
		}

		// 返回 SharpPcap 发送器开启状态
		public bool isOpened()
		{
			return sendDevice.Opened;
		}

		// 设置 帧包、显示设置包 发包参数对象
		public void setParaObjs(FramePacketPara framePacketPara, DispConfigPacketPara dispConfigPacketPara)
		{
			this.framePacketPara = framePacketPara;
			this.dispConfigPacketPara = dispConfigPacketPara;

			if (framePacketPara != null)
			{
				framePacket = pb.buildFramePacket(framePacketPara);
			}
			else
			{
				framePacket = pb.buildFramePacket(0, 0);
			}

			if (dispConfigPacketPara != null)
			{
				dispConfigPacket = pb.buildDispConfigPacket(dispConfigPacketPara);
			}
			else
			{
				dispConfigPacket = pb.buildDispConfigPacket(false, false);
			}
		}

		// 单独发送一个帧包
		public void sendOneFramePacket()
		{
			sendOneReadyPacket(framePacket);
		}

		// 单独发送一个显示设置包
		public void sendDispConfigPacket(int blackScreen, int lockScreen = ParaState.OFF)
		{
            var tempPara = dispConfigPacketPara.Clone();
			// 黑屏
            tempPara.Disp_Black = blackScreen;

			// 锁屏
            tempPara.Disp_Lock = lockScreen;
			
			// 构造显示设置包
            var packet = pb.buildDispConfigPacket(tempPara); 

			// 实际发包
			sendOneReadyPacket(packet);
		}

		// 【专用于在线检测】发送通信命令包 - 定点读命令
		public int sendCommandPacket_DetectRead(string target, int targetDepth, int addr2Read, int topologyDetect_LoopNum)
		{
			int packageId = 0;

			if (target.Equals(InnerCommandTarget.SubCtrl))
			{
				packageId = 100 + targetDepth;				// 读取分控, 通信命令包编号范围 100~355
			}
			else if (target.Equals(InnerCommandTarget.Equipment))
			{
				packageId = 400 + targetDepth;				// 读取附属设备, 通信命令包编号范围 400~463
			}

			byte[] packet = pb.buildCommandPacket4Read(target, targetDepth, packageId, addr2Read);

			sendOneReadyPacket(packet);

			return packageId;
		}

		// 【专用于在线检测】发送显示设置包
		public void sendDispConfigPacket_Detect(bool overall, bool blackScreen)
		{
			byte[] packet = pb.buildDispConfigPacket(overall, blackScreen);
			
			sendOneReadyPacket(packet);
		}
		 
		// 发送 imageBuffer 中的一帧图像
		public void sendOneFrameFromBuffer(byte[] imageBuffer, int channelNumTotal, int height, bool useSwitch)
		{
			bool dispConfigHasChanged = false;

			// 关闭黑屏
			if (dispConfigPacketPara.Disp_Black == ParaState.ON)
			{
				dispConfigPacketPara.Disp_Black = ParaState.OFF;
				dispConfigHasChanged = true;
			}

			// 关闭锁屏
			if (dispConfigPacketPara.Disp_Lock == ParaState.ON)
			{
                dispConfigPacketPara.Disp_Lock = ParaState.OFF;
				dispConfigHasChanged = true;
			}

			// 更新发包对象
			if (dispConfigHasChanged == true)
			{
				dispConfigPacket = pb.buildDispConfigPacket(dispConfigPacketPara);
			}

			/************************************************************************************/

			// 支持交换机
			if (useSwitch == true && pb.macBytesList != null && pb.macBytesList.Count > 0)
			{
				// 先按照交换机端口切分行包
				int splitDataNum = pb.macBytesList.Count;		// 待切分的数据块个数
				List<int> breakPounts = new List<int>();
				for (int i = 0; i < pb.rowMacIndex.Length - 1; i++)
				{
					if (pb.rowMacIndex[i] != pb.rowMacIndex[i + 1])
					{
						breakPounts.Add(i + 1);
					}
				}

				int startRow, endRow;
				for (int i = 0; i < splitDataNum; i++)
				{
					// 计算行包切分的起止位置
					if (i == 0)
					{
						startRow = 0;
					}
					else
					{
						startRow = breakPounts[i - 1];
					}

					if (i == splitDataNum - 1)
					{
						endRow = (height < BaseCoreNet.maxSubEquNum * 8 ? (height - 1) : (BaseCoreNet.maxSubEquNum * 8 - 1));
					}
					else
					{
						endRow = height < breakPounts[i] ? (height - 1) : (breakPounts[i] - 1);
					}

					// 起始行超过结束行, 不发送
					if (startRow > endRow)
					{
						break;
					}

					/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*///sea-201711
					byte[] subImageBuffer = new byte[(endRow - startRow + 1) * channelNumTotal];
					Array.Copy(imageBuffer, startRow * channelNumTotal, subImageBuffer, 0, subImageBuffer.Length);

					if (i == 0)
					{
						sendOneFrame(subImageBuffer, channelNumTotal, endRow - startRow + 1, i, true);
					}
					else
					{
						sendOneFrame(subImageBuffer, channelNumTotal, endRow - startRow + 1, i, false);
					}
					/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
				}
			}
			// 不支持交换机
			else
			{
				sendOneFrame(imageBuffer, channelNumTotal, height, -1, true);
			}
		}

		// 内部函数：发送一帧图像
		private void sendOneFrame(byte[] frameBuffer, int channelNumTotal, int height_pixel, int switchPortIndex, bool withFramePacket)
		{
			int channelNumPerRow = channelNumTotal / height_pixel;

			byte[] sendBuff = new byte[LEDPacket.SHOW_DATA_LENGTH];

			// 分段发送, 每个显示数据包含有 1459 字节的数据, 480像素
			int slipNum = frameBuffer.Length / LEDPacket.SHOW_DATA_LENGTH;		// 需要发送的整包数
			int tailLength = frameBuffer.Length % LEDPacket.SHOW_DATA_LENGTH;		// 结尾剩余字节长度

			byte[] showDataPacket = null;

			// 创建发送队列
			SendQueue squeue = null;
			if (useWinPcap)
			{
				squeue = new SendQueue(frameBuffer.Length + frameBuffer.Length / 2);
			}

			if (withFramePacket)
			{
				// 发送帧包
				byte[] curFramePacket = pb.buildFramePacket(channelNumPerRow, height_pixel);
				if (useWinPcap)
				{
					squeue.Add(curFramePacket);
				}
				else
				{
					if (ip.StartsWith(breadcastIPMask))
					{
						if (socketSendMode == SocketSendMode.Sync)
						{
							socket.SendTo(curFramePacket, epServer);
						}
						else
						{
							socket.BeginSendTo(curFramePacket, 0, curFramePacket.Length, SocketFlags.None, epServer, new AsyncCallback(EndSendData), null);
						}
					}
				}

				// 【可选】发送分控配置包 - 通信包
				if (subConfigSendEnable == true)
				{
					// 构造分控配置通信包
					int packageId = subConfigPacketIndex + 1;
					bool overallFlag = true;
					int memAddr = subConfigMemAddrArray[subConfigPacketIndex];
					byte[] memData = subConfigPacketData[subConfigPacketIndex];
					byte[] packet = pb.buildCommandPacket4Write(InnerCommandTarget.SubCtrl, 0, packageId, (int)memAddr, memData, overallFlag);

					// 发送分控配置通信包
					if (useWinPcap)
					{
						squeue.Add(packet);
					}
					else
					{
						if (ip.StartsWith(breadcastIPMask))
						{
							if (socketSendMode == SocketSendMode.Sync)
							{
								socket.SendTo(packet, epServer);
							}
							else
							{
								socket.BeginSendTo(packet, 0, packet.Length, SocketFlags.None, epServer, new AsyncCallback(EndSendData), null);
							}
						}
					}

					// 更新包索引计数器
					subConfigPacketIndex++;
					if (subConfigPacketIndex == 10)
					{
						subConfigPacketIndex = 0;
					}
				}

				// 发送显示设置包
				if (dispConfigPacket != null)
				{
					if (useWinPcap)
					{
						squeue.Add(dispConfigPacket);
					}
					else
					{
						if (ip.StartsWith(breadcastIPMask))
						{
							if (socketSendMode == SocketSendMode.Sync)
							{
								socket.SendTo(dispConfigPacket, epServer);
							}
							else
							{
								socket.BeginSendTo(dispConfigPacket, 0, dispConfigPacket.Length, SocketFlags.None, epServer, new AsyncCallback(EndSendData), null);
							}
						}
					}
				}
			}

			// 发送数据主体
            DateTime startPoint = DateTime.Now;
            // socket方式10个包为一组
            var pkgList = new List<byte[]>();
			for (int k = 0; k < slipNum; k++)
			{
				Array.Copy(frameBuffer, k * LEDPacket.SHOW_DATA_LENGTH, sendBuff, 0, LEDPacket.SHOW_DATA_LENGTH);

				int rowNum = (k * LEDPacket.SHOW_DATA_LENGTH) / channelNumPerRow;
				int lineNum = (k * LEDPacket.SHOW_DATA_LENGTH) % channelNumPerRow;

                showDataPacket = pb.buildShowDataPacket(sendBuff, Tools.int2Byte2(rowNum), Tools.int2Byte2(lineNum), channelNumPerRow, switchPortIndex);

                var packet = new byte[showDataPacket.Length];
                Array.Copy(showDataPacket, packet, showDataPacket.Length);
                pkgList.Add(packet);

				if (useWinPcap)
				{
					squeue.Add(packet);
				}
				else
				{
                    //if (ip.StartsWith(breadcastIPMask)) {
                    //    if (socketSendMode == SocketSendMode.Sync) {
                    //        socket.SendTo(showDataPacket, epServer);
                    //    } else {
                    //        socket.BeginSendTo(showDataPacket, 0, showDataPacket.Length, SocketFlags.None, epServer, new AsyncCallback(EndSendData), null);
                    //    }
                    //}
				}
            }

            if (pkgList != null && pkgList.Count != 0) {
                var pkgLen = pkgList[0].Length;
                if (useWinPcap) {
                } else {
                    for (var m = 0; m < pkgList.Count; m += 10) {
                        var num = packetNumPerGroup;
                        if (m + packetNumPerGroup > pkgList.Count) {
                            num = pkgList.Count % packetNumPerGroup;
                        }
                        var list = new List<byte>();
                        list.AddRange(pkgList[m]);
                        for (var n = 1; n < num; n++) {
                            var newPacket = pb.addIdleUDPHeader(pkgList[n + m]);
                            list.AddRange(newPacket);
                        }
                        var pkgGroup = list.ToArray();
                        if (ip.StartsWith(breadcastIPMask)) {
                            if (socketSendMode == SocketSendMode.Sync) {
                                socket.SendTo(pkgGroup, epServer);
                            } else {
                                socket.BeginSendTo(pkgGroup, 0, pkgGroup.Length, SocketFlags.None, epServer, new AsyncCallback(EndSendData), null);
                            }
                        }
                    }
                }

            }
            
			DateTime endPoint = DateTime.Now;

			// 以串行队列的形式发送显示数据包
			if (useWinPcap)
			{
				sendDevice.SendQueue(squeue, SendQueueTransmitModes.Normal);
			}

			if (endPoint.Ticks - startPoint.Ticks > 3 * 100 * 10000)
			{
				Log.NormalLogger.Info("SendQueue Timeout,  " + startPoint.Ticks / 10000 + "\t" + endPoint.Ticks / 10000);
			}

			// 发送结尾剩余字节
			if (tailLength > 0)
			{
				sendBuff = new byte[tailLength];

				Array.Copy(frameBuffer, slipNum * LEDPacket.SHOW_DATA_LENGTH, sendBuff, 0, tailLength);

				int rowNum = (slipNum * LEDPacket.SHOW_DATA_LENGTH) / channelNumPerRow;
				int lineNum = (slipNum * LEDPacket.SHOW_DATA_LENGTH) % channelNumPerRow;

				showDataPacket = pb.buildShowDataPacket(sendBuff, Tools.int2Byte2(rowNum), Tools.int2Byte2(lineNum), channelNumPerRow, switchPortIndex);
				
				sendOneReadyPacket(showDataPacket);
			}

			if (useWinPcap)
			{
				squeue.Dispose();
			}
		}

		// 发送构造好的数据包
		public void sendOneReadyPacket(byte[] packet)
		{
            try
            {
                if (useWinPcap)
                {
                    sendDevice.SendPacket(packet);
                }
                else
                {
                    sendPacketBySocket(packet);
                }
            }
            catch (Exception ex)
            {
                Log.NormalLogger.Warn("Catched Exception: " + ex.Message + ex.StackTrace);
                //onCatchException();
            }
		}

		// 设置分控配置数据周期发送使能
		public void setSubConfigSendEnable(bool enable)
		{
			subConfigPacketIndex = 0;
			subConfigSendEnable = enable;
		}

		// 查询分控配置数据发送使能
		public bool getSubConfigSendEnable()
		{
			return subConfigSendEnable;
		}

		// 设置分控配置数据, 10 * 256 bytes
		public void setSubConfigPacketData(List<byte[]> allData)
		{
			if (allData != null && allData.Count == 10)
			{
				subConfigSendEnable = false;

				for (int i = 0; i < 10; i++)
				{
					Array.Copy(allData[i], subConfigPacketData[i], 256);
				}

				subConfigSendEnable = true;
			}
			else
			{
				throw new ArgumentException("SubConfig Data Error");
			}
		}

		// 【PacketBuilder 桥接接口】生成 MAC 映射表
		public void makeMacTables(List<string> macList, List<MacTable> rowMacTable, List<MacTable> subMacTable, List<MacTable> equMacTable)
		{
			this.pb.makeMacTables(macList, rowMacTable, subMacTable, equMacTable);
		}

		// 【PacketBuilder 桥接接口】设置是否支持交换机
		public void setUseSwitch(bool useSwitch)
		{
			this.pb.setUseSwitch(useSwitch);
		}

		// 异步发送消息的委托函数
		private static void EndSendData(IAsyncResult iar)
		{
			try
			{
				socket.EndSend(iar);	// 完成异步发送
			}
			catch(Exception ex)
			{
				Log.NormalLogger.Warn("Catched Exception: " + ex.Message + ex.StackTrace);
			}
		}

		// 使用 socket 发包
		private void sendPacketBySocket(byte[] packet)
		{
			if (ip.StartsWith(breadcastIPMask))
			{
				if (socketSendMode == SocketSendMode.Sync)
				{
					socket.SendTo(packet, epServer);
				}
				else
				{
					socket.BeginSendTo(packet, 0, packet.Length, SocketFlags.None, epServer, new AsyncCallback(EndSendData), null);
				}
			}
		}

		// 设置用户在界面上选择的颜色模式，0-三色（默认），1-四色纯，2-四色增强
		public void setUserColorType(int userColorTypeValue)
		{
			userColorType = userColorTypeValue;
		}

        public void setNetDevice(NetDevice dev)
        {
            this.device = dev;
            dev.packetSender = this;

        }

        ~PacketSender()
        {

        }

        public void sendArtNetPacket(string targetIP, string targetMAC, int messagelen, byte[] message) {
            byte[] winpcapPacket = device.packetBuilder.gengrateArtNetPacket(targetIP, targetMAC, device.getIP(), device.mac, messagelen, message);
            SendOnePacket(winpcapPacket);
        }

        public void sendARPPacket(string targetIP, string targetMAC, int reqType) {
            byte[] winpcapPacket = device.packetBuilder.generateARPPacket(targetIP, targetMAC, device.getIP(), device.mac, reqType);
            SendOnePacket(winpcapPacket);
        }

        private void SendOnePacket(byte[] packet) {
            sendDevice.SendPacket(packet);
        }
	}
}
